package com.cti.commons.delayqueue.binding;

import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Map;
import java.util.Optional;
import java.util.Set;

import org.springframework.aop.framework.Advised;
import org.springframework.aop.support.AopUtils;
import org.springframework.beans.BeansException;
import org.springframework.beans.factory.SmartInitializingSingleton;
import org.springframework.beans.factory.config.BeanPostProcessor;
import org.springframework.context.ApplicationContext;
import org.springframework.context.ApplicationContextAware;
import org.springframework.context.ConfigurableApplicationContext;
import org.springframework.core.annotation.AnnotatedElementUtils;
import org.springframework.stereotype.Component;
import org.springframework.util.Assert;
import org.springframework.util.LinkedMultiValueMap;
import org.springframework.util.MultiValueMap;
import org.springframework.util.ReflectionUtils;
import org.springframework.web.method.support.InvocableHandlerMethod;

import com.cti.commons.delayqueue.config.DelayQueueListener;
import com.cti.commons.delayqueue.core.mode.DelayMessage;
import com.cti.commons.delayqueue.handler.DelayMessageHandlerMethodFactory;
import com.cti.commons.delayqueue.handler.DelayQueueListenerHandler;

@Component
public class DelayQueueListenerAnnotationBeanPostProcessor implements BeanPostProcessor,
    ApplicationContextAware, SmartInitializingSingleton {

    private ConfigurableApplicationContext applicationContext;

    private DelayMessageHandlerMethodFactory messageHandlerMethodFactory;

    private final Set<Runnable> streamListenerCallbacks = new HashSet<>();

    private Set<DelayQueueListenerSetupMethodOrchestrator> queueListenerSetupMethodOrchestrators
        = new LinkedHashSet<>();

    private final MultiValueMap<String, DefaultDelayQueueListenerSetupMethodOrchestrator
        .DelayQueueListenerHandlerMethodMapping> mappedListenerMethods = new LinkedMultiValueMap<>();

    @Override
    public Object postProcessAfterInitialization(Object bean, String beanName) throws BeansException {
        //找到bean对象的真实类型
        Class<?> targetClass = AopUtils.isAopProxy(bean) ? AopUtils.getTargetClass(bean) : bean.getClass();
        //找到bean对象的方法
        Method[] uniqueDeclaredMethods = ReflectionUtils.getUniqueDeclaredMethods(targetClass);
        //逐个遍历并处理声明了@DelayQueueListener注解的方法
        for (Method method : uniqueDeclaredMethods) {
            //找声明了@DelayQueueListener注解的方法
            DelayQueueListener delayQueueListener
                = AnnotatedElementUtils.findMergedAnnotation(method, DelayQueueListener.class);
            if (delayQueueListener != null) {
                streamListenerCallbacks.add(() -> {
                    this.doPostProcess(delayQueueListener, method, bean);
                });
            }
        }
        return bean;
    }

    protected DelayQueueListener postProcessAnnotation(DelayQueueListener originalAnnotation,
        Method annotatedMethod) {
        return originalAnnotation;
    }

    private void doPostProcess(DelayQueueListener delayQueueListener, Method method,
        Object bean) {
        delayQueueListener = postProcessAnnotation(delayQueueListener, method);
        Optional<DelayQueueListenerSetupMethodOrchestrator> orchestratorOptional;
        orchestratorOptional = this.queueListenerSetupMethodOrchestrators.stream()
            .filter(t -> t.supports(method)).findFirst();
        Assert.isTrue(orchestratorOptional.isPresent(),
            "缺少 StreamListenerSetupMethodOrchestrator 实现");
        DelayQueueListenerSetupMethodOrchestrator streamListenerSetupMethodOrchestrator = orchestratorOptional
            .get();
        streamListenerSetupMethodOrchestrator
            .orchestrateStreamListenerSetupMethod(delayQueueListener, method, bean);
    }

    @Override
    public final void afterSingletonsInstantiated() {
        this.injectAndPostProcessDependencies();
        List<DelayQueueListenerHandler> handlers = new ArrayList<>();
        for (Map.Entry<String, List<DefaultDelayQueueListenerSetupMethodOrchestrator
            .DelayQueueListenerHandlerMethodMapping>> mappedBindingEntry : this.mappedListenerMethods
            .entrySet()) {
            for (DefaultDelayQueueListenerSetupMethodOrchestrator.DelayQueueListenerHandlerMethodMapping mapping :
                mappedBindingEntry
                .getValue()) {
                final InvocableHandlerMethod invocableHandlerMethod = this.messageHandlerMethodFactory
                    .createInvocableHandlerMethod(mapping.getTargetBean(),
                        checkProxy(mapping.getMethod(), mapping.getTargetBean()));
                DelayQueueListenerHandler delayQueueListenerHandler = new DelayQueueListenerHandler(mapping.getTopic(),
                    invocableHandlerMethod);
                Assert.isTrue(!handlers.contains(delayQueueListenerHandler), mapping.getTargetBean().getClass()
                    .getName() + "." + mapping.getMethod()
                    .getName() + " @DelayQueueListener 存在相同的topic[" + delayQueueListenerHandler.getTopic() + "]");
                handlers.add(delayQueueListenerHandler);
            }
        }
        this.applicationContext.getBeanFactory().registerSingleton("delayQueueListenerHandler", handlers);
        this.mappedListenerMethods.clear();
    }

    @Override
    public void setApplicationContext(ApplicationContext applicationContext) throws BeansException {
        this.applicationContext = (ConfigurableApplicationContext) applicationContext;
    }

    @SuppressWarnings("rawtypes")
    private final class DefaultDelayQueueListenerSetupMethodOrchestrator implements
        DelayQueueListenerSetupMethodOrchestrator {

        @Override
        public boolean supports(Method method) {
            return true;
        }

        @Override
        public void orchestrateStreamListenerSetupMethod(DelayQueueListener streamListener, Method method,
            Object bean) {
            //校验注解配置项
            String topic = streamListener.topic();
            checkDeclarativeMethod(bean, method, topic);
            registerHandlerMethodOnListened(method, streamListener, bean);
        }

        private boolean checkDeclarativeMethod(Object bean, Method method, String topic) {
            Assert.hasText(topic, bean.getClass()
                .getName() + "." + method.getName() + " @DelayQueueListener topic不能为空值");
            int methodArgumentsLength = method.getParameterCount();
            Assert.isTrue(methodArgumentsLength == 1, bean.getClass()
                .getName() + "." + method.getName() + " @DelayQueueListener 方法参数需要是DelayMessage");
            Class<?>[] parameterTypes = method.getParameterTypes();
            for (int i = 0; i < parameterTypes.length; i++) {
                Class<?> parameterType = parameterTypes[i];
                Assert.isTrue(parameterType.getName()
                    .equals(DelayMessage.class.getName()), bean.getClass()
                    .getName() + "." + method.getName() + " @DelayQueueListener 方法参数需要是DelayMessage");
            }
            return true;
        }

        private void registerHandlerMethodOnListened(Method method,
            DelayQueueListener queueListener, Object bean) {
            final String topic = queueListener.topic();
            DelayQueueListenerAnnotationBeanPostProcessor.this.mappedListenerMethods.add(
                queueListener.topic(),
                new DelayQueueListenerHandlerMethodMapping(bean, method, topic));
        }

        private class DelayQueueListenerHandlerMethodMapping {

            private final Object targetBean;

            private final Method method;

            private final String topic;

            DelayQueueListenerHandlerMethodMapping(Object targetBean, Method method, String topic) {
                this.targetBean = targetBean;
                this.method = method;
                this.topic = topic;
            }

            Object getTargetBean() {
                return this.targetBean;
            }

            Method getMethod() {
                return this.method;
            }

            String getTopic() {
                return topic;
            }

        }

    }

    private void injectAndPostProcessDependencies() {
        this.queueListenerSetupMethodOrchestrators.addAll(this.applicationContext
            .getBeansOfType(DelayQueueListenerSetupMethodOrchestrator.class).values());

        this.queueListenerSetupMethodOrchestrators.add(
            new DefaultDelayQueueListenerSetupMethodOrchestrator());
        this.messageHandlerMethodFactory = this.applicationContext
            .getBean("delayMessageHandlerMethodFactoryImpl", DelayMessageHandlerMethodFactory.class);

        this.streamListenerCallbacks.forEach(Runnable::run);
    }

    private Method checkProxy(Method methodArg, Object bean) {
        Method method = methodArg;
        if (AopUtils.isJdkDynamicProxy(bean)) {
            try {
                method = bean.getClass().getMethod(method.getName(),
                    method.getParameterTypes());
                Class<?>[] proxiedInterfaces = ((Advised) bean).getProxiedInterfaces();
                for (Class<?> iface : proxiedInterfaces) {
                    try {
                        method = iface.getMethod(method.getName(),
                            method.getParameterTypes());
                        break;
                    } catch (NoSuchMethodException noMethod) {
                        noMethod.printStackTrace();
                    }
                }
            } catch (SecurityException ex) {
                ReflectionUtils.handleReflectionException(ex);
            } catch (NoSuchMethodException ex) {
                throw new IllegalStateException(String.format(
                    "@DelayQueueListener 方法 '%s' 在目标类上找到 '%s', "
                        + "但在 bean JDK 代理的任何接口中都找不到。任何一个 "
                        + "将方法拉到接口或切换到子类（CGLIB） "
                        + "通过将 proxy-target-classproxyTargetClass 属性设置为“true”来代理",
                    method.getName(), method.getDeclaringClass().getSimpleName()),
                    ex);
            }
        }
        return method;
    }

}
